2 * Copyright (c) 2008 Apple Inc. All rights reserved.
4 * @APPLE_DTS_LICENSE_HEADER_START@
6 * IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc.
7 * ("Apple") in consideration of your agreement to the following terms, and your
8 * use, installation, modification or redistribution of this Apple software
9 * constitutes acceptance of these terms. If you do not agree with these terms,
10 * please do not use, install, modify or redistribute this Apple software.
12 * In consideration of your agreement to abide by the following terms, and
13 * subject to these terms, Apple grants you a personal, non-exclusive license,
14 * under Apple's copyrights in this original Apple software (the "Apple Software"),
15 * to use, reproduce, modify and redistribute the Apple Software, with or without
16 * modifications, in source and/or binary forms; provided that if you redistribute
17 * the Apple Software in its entirety and without modifications, you must retain
18 * this notice and the following text and disclaimers in all such redistributions
19 * of the Apple Software. Neither the name, trademarks, service marks or logos of
20 * Apple Computer, Inc. may be used to endorse or promote products derived from
21 * the Apple Software without specific prior written permission from Apple. Except
22 * as expressly stated in this notice, no other rights or licenses, express or
23 * implied, are granted by Apple herein, including but not limited to any patent
24 * rights that may be infringed by your derivative works or by other works in
25 * which the Apple Software may be incorporated.
27 * The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
28 * WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
29 * WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30 * PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
31 * COMBINATION WITH YOUR PRODUCTS.
33 * IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
34 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
35 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR
37 * DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF
38 * CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF
39 * APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 * @APPLE_DTS_LICENSE_HEADER_END@
44 #include <dispatch/dispatch.h>
51 #include <sys/types.h>
52 #include <sys/socket.h>
53 #include <sys/fcntl.h>
60 #include <sys/param.h>
61 #include <sys/ioctl.h>
62 #include <mach/mach.h>
68 #define dlog(a) dispatch_debug(a, #a)
70 #define dlog(a) do { } while(0)
74 void *run_block(void *);
75 void setup_fd_relay(int netfd
/* bidirectional */,
76 int infd
/* local input */,
77 int outfd
/* local output */,
78 void (^finalizer_block
)(void));
79 void doreadwrite(int fd1
, int fd2
, char *buffer
, size_t len
);
81 #define BUFFER_SIZE 1099
83 int main(int argc
, char *argv
[]) {
86 bool use_v4_only
= false, use_v6_only
= false;
87 bool debug
= false, no_stdin
= false;
88 bool keep_listening
= false, do_listen
= false;
89 bool do_loookups
= true, verbose
= false;
90 bool do_udp
= false, do_bind_ip
= false, do_bind_port
= false;
91 const char *hostname
, *servname
;
93 struct addrinfo hints
, *aires
, *aires0
;
94 const char *bind_hostname
, *bind_servname
;
97 dispatch_group_t listen_group
= NULL
;
99 while ((ch
= getopt(argc
, argv
, "46Ddhklnvup:s:")) != -1) {
117 keep_listening
= true;
133 bind_servname
= optarg
;
137 bind_hostname
= optarg
;
149 if (use_v4_only
&& use_v6_only
) {
150 errx(EX_USAGE
, "-4 and -6 specified");
153 if (keep_listening
&& !do_listen
) {
154 errx(EX_USAGE
, "-k specified but no -l");
157 if (do_listen
&& (do_bind_ip
|| do_bind_port
)) {
158 errx(EX_USAGE
, "-p or -s option with -l");
165 } else if (argc
>= 1) {
169 errx(EX_USAGE
, "No service name provided");
176 errx(EX_USAGE
, "No hostname and service name provided");
180 if (do_bind_ip
|| do_bind_port
) {
182 bind_hostname
= NULL
;
185 bind_servname
= NULL
;
189 openlog(getprogname(), LOG_PERROR
|LOG_CONS
, LOG_DAEMON
);
190 setlogmask(debug
? LOG_UPTO(LOG_DEBUG
) : verbose
? LOG_UPTO(LOG_INFO
) : LOG_UPTO(LOG_ERR
));
192 dq
= dispatch_queue_create("netcat", NULL
);
193 listen_group
= dispatch_group_create();
195 bzero(&hints
, sizeof(hints
));
196 hints
.ai_family
= use_v4_only
? PF_INET
: (use_v6_only
? PF_INET6
: PF_UNSPEC
);
197 hints
.ai_socktype
= do_udp
? SOCK_DGRAM
: SOCK_STREAM
;
198 hints
.ai_protocol
= do_udp
? IPPROTO_UDP
: IPPROTO_TCP
;
199 hints
.ai_flags
= (!do_loookups
? AI_NUMERICHOST
| AI_NUMERICSERV
: 0) | (do_listen
? AI_PASSIVE
: 0);
201 ret
= getaddrinfo(hostname
, servname
, &hints
, &aires0
);
203 errx(1, "getaddrinfo(%s, %s): %s", hostname
, servname
, gai_strerror(ret
));
206 for (aires
= aires0
; aires
; aires
= aires
->ai_next
) {
208 // asynchronously set up the socket
210 dispatch_group_async(listen_group
,
211 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0),
214 dispatch_source_t ds
;
216 s
= socket(aires
->ai_family
, aires
->ai_socktype
, aires
->ai_protocol
);
222 if(setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, (const char *)&val
, sizeof(val
)) < 0) {
223 warn("Could not set SO_REUSEADDR");
226 if(setsockopt(s
, SOL_SOCKET
, SO_REUSEPORT
, (const char *)&val
, sizeof(val
)) < 0) {
227 warn("Could not set SO_REUSEPORT");
230 if(setsockopt(s
, SOL_SOCKET
, SO_NOSIGPIPE
, &val
, sizeof(val
)) < 0) {
231 warn("Could not set SO_NOSIGPIPE");
234 if (bind(s
, aires
->ai_addr
, aires
->ai_addrlen
) < 0) {
241 syslog(LOG_DEBUG
, "listening on socket %d", s
);
242 ds
= dispatch_source_create(DISPATCH_SOURCE_TYPE_READ
, s
, 0, dq
);
243 dispatch_source_set_event_handler(ds
, ^{
244 // got an incoming connection
245 int s2
, lfd
= dispatch_source_get_handle(ds
);
246 dispatch_queue_t listen_queue
= dispatch_get_current_queue();
248 // prevent further accept(2)s across multiple sources
249 dispatch_retain(listen_queue
);
250 dispatch_suspend(listen_queue
);
253 // lfd is our socket, but let's connect in the reverse
254 // direction to set up the connection fully
256 struct sockaddr_storage sockin
;
261 socklen
= sizeof(sockin
);
262 peeklen
= recvfrom(lfd
, udpbuf
, sizeof(udpbuf
),
263 MSG_PEEK
, (struct sockaddr
*)&sockin
, &socklen
);
266 dispatch_resume(listen_queue
);
267 dispatch_release(listen_queue
);
271 cret
= connect(lfd
, (struct sockaddr
*)&sockin
, socklen
);
274 dispatch_resume(listen_queue
);
275 dispatch_release(listen_queue
);
280 syslog(LOG_DEBUG
, "accepted socket %d", s2
);
282 s2
= accept(lfd
, NULL
, NULL
);
285 dispatch_resume(listen_queue
);
286 dispatch_release(listen_queue
);
289 syslog(LOG_DEBUG
, "accepted socket %d -> %d", lfd
, s2
);
293 setup_fd_relay(s2
, no_stdin
? -1 : STDIN_FILENO
, STDOUT_FILENO
, ^{
297 dispatch_resume(listen_queue
);
298 dispatch_release(listen_queue
);
299 if (!keep_listening
) {
305 dispatch_release(dq
);
308 // synchronously try each address to try to connect
309 __block
bool did_connect
= false;
314 s
= socket(aires
->ai_family
, aires
->ai_socktype
, aires
->ai_protocol
);
320 if(setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, (const char *)&val
, sizeof(val
)) < 0) {
321 warn("Could not set SO_REUSEADDR");
324 if(setsockopt(s
, SOL_SOCKET
, SO_REUSEPORT
, (const char *)&val
, sizeof(val
)) < 0) {
325 warn("Could not set SO_REUSEPORT");
328 if(setsockopt(s
, SOL_SOCKET
, SO_NOSIGPIPE
, &val
, sizeof(val
)) < 0) {
329 warn("Could not set SO_NOSIGPIPE");
332 if (do_bind_port
|| do_bind_ip
) {
333 struct addrinfo bhints
, *bind_aires
;
337 bzero(&bhints
, sizeof(bhints
));
338 bhints
.ai_family
= aires
->ai_family
;
339 bhints
.ai_socktype
= aires
->ai_socktype
;
340 bhints
.ai_protocol
= aires
->ai_protocol
;
341 bhints
.ai_flags
= (do_bind_ip
? AI_NUMERICHOST
: 0) | (do_bind_port
? AI_NUMERICSERV
: 0) | AI_PASSIVE
;
343 bret
= getaddrinfo(bind_hostname
, bind_servname
, &bhints
, &bind_aires
);
345 warnx("getaddrinfo(%s, %s): %s", bind_hostname
, bind_servname
, gai_strerror(bret
));
347 freeaddrinfo(bind_aires
);
351 switch(bind_aires
->ai_family
) {
353 bport
= ((struct sockaddr_in
*)bind_aires
->ai_addr
)->sin_port
;
356 bport
= ((struct sockaddr_in6
*)bind_aires
->ai_addr
)->sin6_port
;
363 if (ntohs(bport
) > 0 && ntohs(bport
) < IPPORT_RESERVED
) {
364 bret
= bindresvport_sa(s
, (struct sockaddr
*)bind_aires
->ai_addr
);
366 bret
= bind(s
, bind_aires
->ai_addr
, bind_aires
->ai_addrlen
);
372 freeaddrinfo(bind_aires
);
376 freeaddrinfo(bind_aires
);
379 if (connect(s
, aires
->ai_addr
, aires
->ai_addrlen
) < 0) {
380 syslog(LOG_INFO
, "connect to %s port %s (%s) failed: %s",
383 aires
->ai_protocol
== IPPROTO_TCP
? "tcp" : aires
->ai_protocol
== IPPROTO_UDP
? "udp" : "unknown",
389 syslog(LOG_INFO
, "Connection to %s %s port [%s] succeeded!",
392 aires
->ai_protocol
== IPPROTO_TCP
? "tcp" : aires
->ai_protocol
== IPPROTO_UDP
? "udp" : "unknown");
396 // netcat sends a few bytes to set up the connection
397 doreadwrite(-1, s
, "XXXX", 4);
400 setup_fd_relay(s
, no_stdin
? -1 : STDIN_FILENO
, STDOUT_FILENO
, ^{
412 dispatch_group_wait(listen_group
, DISPATCH_TIME_FOREVER
);
413 freeaddrinfo(aires0
);
415 if (!do_listen
&& aires
== NULL
) {
416 // got to the end of the address list without connecting
427 fprintf(stderr
, "Usage: %s [-4] [-6] [-D] [-d] [-h] [-k] [-l] [-n] [-v]\n", getprogname());
428 fprintf(stderr
, " \t[-u] [-p <source_port>] [-s <source_ip>]\n");
432 void *run_block(void *arg
)
434 void (^b
)(void) = (void (^)(void))arg
;
444 * Read up-to as much as is requested, and write
445 * that to the other fd, taking into account exceptional
446 * conditions and re-trying
448 void doreadwrite(int fd1
, int fd2
, char *buffer
, size_t len
) {
449 ssize_t readBytes
, writeBytes
, totalWriteBytes
;
452 syslog(LOG_DEBUG
, "trying to read %ld bytes from fd %d", len
, fd1
);
453 readBytes
= read(fd1
, buffer
, len
);
455 if (errno
== EINTR
|| errno
== EAGAIN
) {
456 /* can't do anything now, hope we get called again */
457 syslog(LOG_DEBUG
, "error read fd %d: %s (%d)", fd1
, strerror(errno
), errno
);
460 err(1, "read fd %d", fd1
);
462 } else if (readBytes
== 0) {
463 syslog(LOG_DEBUG
, "EOF on fd %d", fd1
);
466 syslog(LOG_DEBUG
, "read %ld bytes from fd %d", readBytes
, fd1
);
469 syslog(LOG_DEBUG
, "read buffer has %ld bytes", readBytes
);
474 writeBytes
= write(fd2
, buffer
+totalWriteBytes
, readBytes
-totalWriteBytes
);
475 if (writeBytes
< 0) {
476 if (errno
== EINTR
|| errno
== EAGAIN
) {
479 err(1, "write fd %d", fd2
);
482 syslog(LOG_DEBUG
, "wrote %ld bytes to fd %d", writeBytes
, fd2
);
483 totalWriteBytes
+= writeBytes
;
485 } while (totalWriteBytes
< readBytes
);
491 * We set up dispatch sources for netfd and infd.
492 * Since only one callback is called at a time per-source,
493 * we don't need any additional serialization, and the network
494 * and infd could be read from at the same time.
496 void setup_fd_relay(int netfd
/* bidirectional */,
497 int infd
/* local input */,
498 int outfd
/* local output */,
499 void (^finalizer_block
)(void))
501 dispatch_source_t netsource
= NULL
, insource
= NULL
;
503 dispatch_queue_t teardown_queue
= dispatch_queue_create("teardown_queue", NULL
);
505 void (^finalizer_block_copy
)(void) = _Block_copy(finalizer_block
); // release after calling
506 void (^cancel_hander
)(dispatch_source_t source
) = ^(dispatch_source_t source
){
508 dlog(teardown_queue
);
511 * allowing the teardown queue to become runnable will get
512 * the teardown block scheduled, which will cancel all other
513 * sources and call the client-supplied finalizer
515 dispatch_resume(teardown_queue
);
516 dispatch_release(teardown_queue
);
518 void (^event_handler
)(dispatch_source_t source
, int wfd
) = ^(dispatch_source_t source
, int wfd
) {
519 int rfd
= dispatch_source_get_handle(source
);
520 size_t bytesAvail
= dispatch_source_get_data(source
);
523 syslog(LOG_DEBUG
, "dispatch source %d -> %d has %lu bytes available",
524 rfd
, wfd
, bytesAvail
);
525 if (bytesAvail
== 0) {
527 dispatch_source_cancel(source
);
530 buffer
= malloc(BUFFER_SIZE
);
531 doreadwrite(rfd
,wfd
, buffer
, MIN(BUFFER_SIZE
, bytesAvail
+2));
536 * Suspend this now twice so that neither source can accidentally resume it
537 * while we're still setting up the teardown block. When either source
538 * gets an EOF, the queue is resumed so that it can teardown the other source
539 * and call the client-supplied finalizer
541 dispatch_suspend(teardown_queue
);
542 dispatch_suspend(teardown_queue
);
545 dispatch_retain(teardown_queue
); // retain so that we can resume in this block later
547 dlog(teardown_queue
);
549 // since the event handler serializes, put this on a concurrent queue
550 insource
= dispatch_source_create(DISPATCH_SOURCE_TYPE_READ
, infd
, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0));
551 dispatch_source_set_event_handler(insource
, ^{ event_handler(insource
, netfd
); });
552 dispatch_source_set_cancel_handler(insource
, ^{ cancel_hander(insource
); });
553 dispatch_resume(insource
);
557 dispatch_retain(teardown_queue
); // retain so that we can resume in this block later
559 dlog(teardown_queue
);
561 // since the event handler serializes, put this on a concurrent queue
562 netsource
= dispatch_source_create(DISPATCH_SOURCE_TYPE_READ
, netfd
, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0));
563 dispatch_source_set_event_handler(netsource
, ^{ event_handler(netsource
, outfd
); });
564 dispatch_source_set_cancel_handler(netsource
, ^{ cancel_hander(netsource
); });
565 dispatch_resume(netsource
);
568 dispatch_async(teardown_queue
, ^{
569 syslog(LOG_DEBUG
, "Closing connection on fd %d -> %d -> %d", infd
, netfd
, outfd
);
573 dispatch_source_cancel(insource
);
574 dispatch_release(insource
); // matches initial create
579 dispatch_source_cancel(netsource
);
580 dispatch_release(netsource
); // matches initial create
583 dlog(teardown_queue
);
585 finalizer_block_copy();
586 _Block_release(finalizer_block_copy
);
589 /* Resume this once so their either source can do the second resume
590 * to start the teardown block running
592 dispatch_resume(teardown_queue
);
593 dispatch_release(teardown_queue
); // matches initial create
594 dlog(teardown_queue
);